/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.context.NullContext;
import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slotchain.SlotChainProvider;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.Rule;

/**
 * {@inheritDoc}
 *
 * @author jialiang.linjl
 * @author leyou(lihao)
 * @author Eric Zhao
 * @see Sph
 */
public class CtSph implements Sph {

  private static final Object[] OBJECTS0 = new Object[0];

  /**
   * Same resource({@link ResourceWrapper#equals(Object)}) will share the same {@link
   * ProcessorSlotChain}, no matter in which {@link Context}.
   */
  private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
      = new HashMap<ResourceWrapper, ProcessorSlotChain>();

  private static final Object LOCK = new Object();

  private AsyncEntry asyncEntryWithNoChain(ResourceWrapper resourceWrapper, Context context) {
    AsyncEntry entry = new AsyncEntry(resourceWrapper, null, context);
    entry.initAsyncContext();
    // The async entry will be removed from current context as soon as it has been created.
    entry.cleanCurrentEntryInLocal();
    return entry;
  }

  private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrapper, int count,
      boolean prioritized,
      Object... args) throws BlockException {
    Context context = ContextUtil.getContext();
    if (context instanceof NullContext) {
      // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
      // so here init the entry only. No rule checking will be done.
      return asyncEntryWithNoChain(resourceWrapper, context);
    }
    if (context == null) {
      // Using default context.
      context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    // Global switch is turned off, so no rule checking will be done.
    if (!Constants.ON) {
      return asyncEntryWithNoChain(resourceWrapper, context);
    }

    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    // Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done.
    if (chain == null) {
      return asyncEntryWithNoChain(resourceWrapper, context);
    }

    AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context);
    try {
      chain.entry(context, resourceWrapper, null, count, prioritized, args);
      // Initiate the async context only when the entry successfully passed the slot chain.
      asyncEntry.initAsyncContext();
      // The asynchronous call may take time in background, and current context should not be hanged on it.
      // So we need to remove current async entry from current context.
      asyncEntry.cleanCurrentEntryInLocal();
    } catch (BlockException e1) {
      // When blocked, the async entry will be exited on current context.
      // The async context will not be initialized.
      asyncEntry.exitForContext(context, count, args);
      throw e1;
    } catch (Throwable e1) {
      // This should not happen, unless there are errors existing in Sentinel internal.
      // When this happens, async context is not initialized.
      RecordLog.warn("Sentinel unexpected exception in asyncEntryInternal", e1);

      asyncEntry.cleanCurrentEntryInLocal();
    }
    return asyncEntry;
  }

  private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count,
      Object... args)
      throws BlockException {
    return asyncEntryWithPriorityInternal(resourceWrapper, count, false, args);
  }

  /**
   * @param resourceWrapper 资源实例
   * @param count           默认值为 1
   * @param prioritized     默认值为 false
   * @param args
   * @return com.alibaba.csp.sentinel.Entry
   * @author tanghailan
   * @date 2021/10/1 8:41
   */
  private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized,
      Object... args)
      throws BlockException {
    //从 ThreadLocal 中获取 context
    //即一个请求会创建一个线程 ，一个线程绑定一个context
    Context context = ContextUtil.getContext();
    //若context 是 NullContext 类型，则表示当前系统中的context数量已经超过阈值
    //即访问请求的数量已经超出了阈值，此时直接返回一个无需做规则检测的资源操作对象
    if (context instanceof NullContext) {
      // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
      // so here init the entry only. No rule checking will be done.
      return new CtEntry(resourceWrapper, null, context);
    }

    //若当前线程中没有绑定context，则创建一个context并将其放入ThreadLocal中
    if (context == null) {
      // Using default context.
      context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    //若全局开关是关闭的，则直接返回一个无需做规则检测的资源操作对象
    // Global switch is close, no rule checking will do.
    if (!Constants.ON) {
      return new CtEntry(resourceWrapper, null, context);
    }

    //查找SlotChain
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

    /*
     * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
     * so no rule checking will be done.
     */
    //若没有找到chain，则意味着chain数量超出了阈值
    if (chain == null) {
      return new CtEntry(resourceWrapper, null, context);
    }

    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
      chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
      e.exit(count, args);
      throw e1;
    } catch (Throwable e1) {
      // This should not happen, unless there are errors existing in Sentinel internal.
      RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
  }

  /**
   * Do all {@link Rule}s checking about the resource.
   *
   * <p>Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource
   * will use same {@link ProcessorSlot} globally. </p>
   *
   * <p>Note that total {@link ProcessorSlot} count must not exceed {@link
   * Constants#MAX_SLOT_CHAIN_SIZE}, otherwise no rules checking will do. In this condition, all
   * requests will pass directly, with no checking or exception.</p>
   *
   * @param resourceWrapper resource name
   * @param count           tokens needed
   * @param args            arguments of user method call
   * @return {@link Entry} represents this call
   * @throws BlockException if any rule's threshold is exceeded
   */
  public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args)
      throws BlockException {
    return entryWithPriority(resourceWrapper, count, false, args);
  }

  /**
   * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will be created
   * if the resource doesn't relate one.
   *
   * <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
   * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.<p/>
   *
   * <p>
   * Note that total {@link ProcessorSlot} count must not exceed {@link
   * Constants#MAX_SLOT_CHAIN_SIZE}, otherwise null will return.
   * </p>
   *
   * @param resourceWrapper target resource
   * @return {@link ProcessorSlotChain} of the resource
   */
  ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
      synchronized (LOCK) {
        chain = chainMap.get(resourceWrapper);
        if (chain == null) {
          // Entry size limit.
          if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
            return null;
          }

          chain = SlotChainProvider.newSlotChain();
          Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
              chainMap.size() + 1);
          newMap.putAll(chainMap);
          newMap.put(resourceWrapper, chain);
          chainMap = newMap;
        }
      }
    }
    return chain;
  }

  /**
   * Get current size of created slot chains.
   *
   * @return size of created slot chains
   * @since 0.2.0
   */
  public static int entrySize() {
    return chainMap.size();
  }

  /**
   * Reset the slot chain map. Only for internal test.
   *
   * @since 0.2.0
   */
  static void resetChainMap() {
    chainMap.clear();
  }

  /**
   * Only for internal test.
   *
   * @since 0.2.0
   */
  static Map<ResourceWrapper, ProcessorSlotChain> getChainMap() {
    return chainMap;
  }

  /**
   * This class is used for skip context name checking.
   */
  private final static class InternalContextUtil extends ContextUtil {

    static Context internalEnter(String name) {
      return trueEnter(name, "");
    }

    static Context internalEnter(String name, String origin) {
      return trueEnter(name, origin);
    }
  }

  @Override
  public Entry entry(String name) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
    return entry(resource, 1, OBJECTS0);
  }

  @Override
  public Entry entry(Method method) throws BlockException {
    MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
    return entry(resource, 1, OBJECTS0);
  }

  @Override
  public Entry entry(Method method, EntryType type) throws BlockException {
    MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
    return entry(resource, 1, OBJECTS0);
  }

  @Override
  public Entry entry(String name, EntryType type) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entry(resource, 1, OBJECTS0);
  }

  @Override
  public Entry entry(Method method, EntryType type, int count) throws BlockException {
    MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
    return entry(resource, count, OBJECTS0);
  }

  @Override
  public Entry entry(String name, EntryType type, int count) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entry(resource, count, OBJECTS0);
  }

  @Override
  public Entry entry(Method method, int count) throws BlockException {
    MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
    return entry(resource, count, OBJECTS0);
  }

  @Override
  public Entry entry(String name, int count) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
    return entry(resource, count, OBJECTS0);
  }

  @Override
  public Entry entry(Method method, EntryType type, int count, Object... args)
      throws BlockException {
    MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
    return entry(resource, count, args);
  }

  @Override
  public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entry(resource, count, args);
  }

  @Override
  public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args)
      throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return asyncEntryInternal(resource, count, args);
  }

  @Override
  public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized)
      throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entryWithPriority(resource, count, prioritized);
  }

  @Override
  public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized,
      Object... args)
      throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entryWithPriority(resource, count, prioritized, args);
  }

  @Override
  public Entry entryWithType(String name, int resourceType, EntryType entryType, int count,
      Object[] args)
      throws BlockException {
    //count 参数:表示当前请求可以增加多少计数
    //注意第五个参数为false
    return entryWithType(name, resourceType, entryType, count, false, args);
  }

  @Override
  public Entry entryWithType(String name, int resourceType, EntryType entryType, int count,
      boolean prioritized,
      Object[] args) throws BlockException {
    //将信息封装为一个资源对象
    StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
    //返回一个资源操作对象Entry
    //prioritized 若为true 则表示当前访问必须等待 "根据其优先级计算出的时间" 后才可通过
    //prioritized 若为false  则表示当前请求无需等待
    return entryWithPriority(resource, count, prioritized, args);
  }

  @Override
  public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType,
      int count,
      boolean prioritized, Object[] args) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
    return asyncEntryWithPriorityInternal(resource, count, prioritized, args);
  }
}
